/** @file oct_object.cpp
    @brief Octree object */

#include "oct/oct_object.h"

//==============================================================================
// Constructors
//==============================================================================
G3::OCT_FILE_HEADER::OCT_FILE_HEADER () {
    magic [0] = 'O';
    magic [1] = 'C';
    magic [2] = 'T';
    magic [3] = '4';

    num_verts = 0;
    num_texels = 0;
    num_normals = 0;
    num_tangents = 0;
    num_indexes = 0;
    num_materials = 0;
    materialchunk_size = 0;
    num_nodes = 0;
    num_leaves = 0;
	num_batches = 0;
}

G3::OCT_FILE_NODE::OCT_FILE_NODE () {
    for (int i=0; i<8; i++)
        children [i] = 0;
}

G3::OCT_FILE_LEAF::OCT_FILE_LEAF () {
    num_batches = 0;
    batch_offset = 0;
}

G3::OCT_FILE_BATCH::OCT_FILE_BATCH () {
    matID = 0;
    indexStart = 0;
    numElements = 0;
}

G3::OCT_OBJECT::OCT_OBJECT () {
    vbo = NULL;
    dlist = NULL;
	gptr = NULL;

    log = &G3::MainLog;

    mat = NULL;
    uv = NULL;
    n = NULL;
    t = NULL;

    zStream = NULL;
    unzStream = NULL;
}

//==============================================================================
// Copy constructors
//==============================================================================
G3::OCT_FILE_HEADER::OCT_FILE_HEADER (const OCT_FILE_HEADER &hdr) {
    for (int i=0; i<4; i++)
        magic [i] = hdr.magic [i];

    num_verts = hdr.num_verts;
    num_texels = hdr.num_texels;
    num_normals = hdr.num_normals;
    num_tangents = hdr.num_tangents;
    num_indexes = hdr.num_indexes;
    num_materials = hdr.num_materials;
    materialchunk_size = hdr.materialchunk_size;
    num_nodes = hdr.num_nodes;
    num_leaves = hdr.num_leaves;
	num_batches = hdr.num_batches;
}

G3::OCT_FILE_NODE::OCT_FILE_NODE (const OCT_FILE_NODE &node) {
    BBox = node.BBox;

    for (int i=0; i<8; i++)
        children [i] = node.children [i];
}

G3::OCT_FILE_LEAF::OCT_FILE_LEAF (const OCT_FILE_LEAF &leaf) {
    BBox = leaf.BBox;
    num_batches = leaf.num_batches;
    batch_offset = leaf.batch_offset;
}


G3::OCT_FILE_BATCH::OCT_FILE_BATCH (const OCT_FILE_BATCH &batch) {
    BBox = batch.BBox;
    matID = batch.matID;
    indexStart = batch.indexStart;
    numElements = batch.numElements;
}

G3::OCT_OBJECT::OCT_OBJECT (const OCT_OBJECT &obj) {
    Assert (false, "There should be no OCT_OBJECT copies..");

    filepath = obj.filepath;
    stream = obj.stream;
    priority = obj.priority;
    filepos = obj.filepos;
    chunk_size = obj.chunk_size;
    timer = obj.timer;

    fHeader = obj.fHeader;

    nodes = obj.nodes;
    leaves = obj.leaves;
    batches = obj.batches;
    vertices = obj.vertices;
    texcoords = obj.texcoords;
    normals = obj.normals;
    tangents = obj.tangents;
    indexes = obj.indexes;
    materials = obj.materials;
    material_files = obj.material_files;

    vbo = obj.vbo;
    dlist = obj.dlist;
    gptr = obj.gptr;

    log = obj.log;

    mat = obj.mat;
    uv = obj.uv;
    n = obj.n;
    t = obj.t;
    zStream = obj.zStream;
    unzStream = obj.unzStream;
}

//==============================================================================
// Destructors
//==============================================================================
G3::OCT_OBJECT::~OCT_OBJECT () {
    clear ();
}

//==============================================================================
// Assignment operators
//==============================================================================
G3::OCT_FILE_HEADER& G3::OCT_FILE_HEADER::operator= (const G3::OCT_FILE_HEADER &hdr) {
    for (int i=0; i<4; i++)
        magic [i] = hdr.magic [i];

    num_verts = hdr.num_verts;
    num_texels = hdr.num_texels;
    num_normals = hdr.num_normals;
    num_tangents = hdr.num_tangents;
    num_indexes = hdr.num_indexes;
    num_materials = hdr.num_materials;
    materialchunk_size = hdr.materialchunk_size;
    num_nodes = hdr.num_nodes;
    num_leaves = hdr.num_leaves;
	num_batches = hdr.num_batches;

	return *this;
}

G3::OCT_FILE_NODE& G3::OCT_FILE_NODE::operator= (const G3::OCT_FILE_NODE &node) {
	BBox = node.BBox;

    for (int i=0; i<8; i++)
        children [i] = node.children [i];

    return *this;
}

G3::OCT_FILE_LEAF& G3::OCT_FILE_LEAF::operator= (const G3::OCT_FILE_LEAF &leaf) {
	BBox = leaf.BBox;

    num_batches = leaf.num_batches;
    batch_offset = leaf.batch_offset;

    return *this;
}

G3::OCT_FILE_BATCH& G3::OCT_FILE_BATCH::operator= (const OCT_FILE_BATCH &batch) {
	BBox = batch.BBox;
    matID = batch.matID;
    indexStart = batch.indexStart;
    numElements = batch.numElements;

    return *this;
}

G3::OCT_OBJECT& G3::OCT_OBJECT::operator= (const G3::OCT_OBJECT &obj) {
    Assert (false, "There should be no OCT_OBJECT assignments..");

    filepath = obj.filepath;
    stream = obj.stream;
    priority = obj.priority;
    filepos = obj.filepos;
    chunk_size = obj.chunk_size;
    timer = obj.timer;

    fHeader = obj.fHeader;

    nodes = obj.nodes;
    leaves = obj.leaves;
    batches = obj.batches;
    vertices = obj.vertices;
    texcoords = obj.texcoords;
    normals = obj.normals;
    tangents = obj.tangents;
    indexes = obj.indexes;
    materials = obj.materials;
    material_files = obj.material_files;

    vbo = obj.vbo;
    dlist = obj.dlist;
    gptr = obj.gptr;

    log = obj.log;

    mat = obj.mat;
    uv = obj.uv;
    n = obj.n;
    t = obj.t;
    zStream = obj.zStream;
    unzStream = obj.unzStream;

    return *this;
}

//==============================================================================
// Writes the OCT header
//==============================================================================
void G3::OCT_OBJECT::writeHeader () {
    Assert (zStream, "Attempting to write into a non-existent stream..");

    // Write the header
    if (!zStream->Deflate ((byte *) &fHeader, sizeof (OCT_FILE_HEADER)))
        throw G3::EX ("Failed to write file header", FILE_LINE);
}

//==============================================================================
// Writes OCT materials
//==============================================================================
void G3::OCT_OBJECT::writeMaterials () {
    Assert (zStream, "Attempting to write into a non-existent stream..");
	Assert (fHeader.num_materials == (int) material_files.size (), "There is an inconsistency in the number of materials..");

    char c = 0;

    // Write the materials
    for (int i=0; i<fHeader.num_materials; i++) {
        if (!zStream->Deflate ((byte *) material_files [i].c_str (), material_files [i].size ()))
            throw G3::EX_INT ("Failed to write material path nr. ", i, FILE_LINE);

        if (!zStream->Deflate ((byte *) &c, 1))
            throw G3::EX_INT ("Failed to write an EOL to material path nr. ", i, FILE_LINE);
    }
}

//==============================================================================
// Writes OCT nodes
//==============================================================================
void G3::OCT_OBJECT::writeNodes () {
    Assert (zStream, "Attempting to write into a non-existent stream..");
	Assert (fHeader.num_nodes == (int) nodes.size (), "There is an inconsistency in the number of nodes..");

	OCT_FILE_NODE ofnode;

    for (int i=0; i<fHeader.num_nodes; i++) {
        ofnode.BBox = nodes [i].BBox;

        for (int j=0; j<8; j++)
            ofnode.children [j] = nodes [i].children [j];

        if (!zStream->Deflate ((byte *) &ofnode, sizeof (OCT_FILE_NODE)))
            throw G3::EX_INT ("Failed to write node no. ", i, FILE_LINE);
    }
}

//==============================================================================
// Writes OCT leaves
//==============================================================================
void G3::OCT_OBJECT::writeLeaves () {
    Assert (zStream, "Attempting to write into a non-existent stream..");
	Assert (fHeader.num_leaves == (int) leaves.size (), "There is an inconsistency in the number of leaves..");

	OCT_FILE_LEAF ofleaf;

    for (int i=0; i<fHeader.num_leaves; i++) {
        ofleaf.BBox = leaves [i].BBox;
        ofleaf.num_batches = leaves [i].num_batches;
        ofleaf.batch_offset = leaves [i].batch_offset;

        if (!zStream->Deflate ((byte *) &ofleaf, sizeof (OCT_FILE_LEAF)))
            throw G3::EX_INT ("Failed to write leaf no. ", i, FILE_LINE);
    }
}

//==============================================================================
// Writes OCT batches
//==============================================================================
void G3::OCT_OBJECT::writeBatches () {
    Assert (zStream, "Attempting to write into a non-existent stream..");
	Assert (fHeader.num_batches == (int) batches.size (), "There is an inconsistency in the number of batches..");

    OCT_FILE_BATCH fBatch;

    for (int i=0; i<fHeader.num_batches; i++) {
        fBatch.BBox = batches [i].BBox;
        fBatch.matID = batches [i].matID;
        fBatch.indexStart = batches [i].indexStart;
        fBatch.numElements = batches [i].numElements;

        G3::MainLog.Report ("> Batch %d NumElements %d\n", i, fBatch.numElements);

        if (!zStream->Deflate ((byte *) &fBatch, sizeof (OCT_FILE_BATCH)))
            throw G3::EX_INT ("Failed to write batch no. ", i, FILE_LINE);
    }
}

//! \todo Streamed writing
//==============================================================================
// Saves the OCT object
//==============================================================================
bool G3::OCT_OBJECT::save (const std::string &filename) {
    int i;

    try {
        if (unzStream)
            delete unzStream;
        // 8 MB of packed buffer
        unzStream = new G3::CISTREAM (8388608);
    } catch (...) {
        if (log)
            log->Report ("Failed to create a CISTREAM for 8 MB of buffer..\n");
    }

    try {
        if (zStream)
            delete zStream;

        zStream = new G3::ZIPSTREAM (unzStream);

        if (!zStream->Open (ZSTREAM_DEFLATE, 9))
            throw G3::EX ("Failed to open a ZIPSTREAM for level 9 deflation", FILE_LINE);
    } catch (G3::EX ex) {
        if (log)
            log->Report ("OCT_OBJECT::save: %s...\n", ex.toString ().c_str ());
        return false;
    } catch (...) {
        if (log)
            log->Report ("Failed to create a ZIPSTREAM for compressed streaming..\n");
        return false;
    }

    fHeader.num_verts = vertices.size ();
    fHeader.num_texels = texcoords.size ();
    fHeader.num_normals = normals.size ();
    fHeader.num_tangents = tangents.size ();
    fHeader.num_indexes = indexes.size ();
    fHeader.num_materials = material_files.size ();
    fHeader.num_nodes = nodes.size ();
    fHeader.num_leaves = leaves.size ();
    fHeader.num_batches = batches.size ();

    fHeader.materialchunk_size = 0;
    for (i=0; i<(int)material_files.size (); i++) {
        fHeader.materialchunk_size += (material_files [i].length () + 1) * sizeof (char);
    }

    try {
        writeHeader ();

        // G3::VEC3F vertices
        for (i=0; i<(int)vertices.size (); i++) {
            if (!zStream->Deflate ((byte *) &vertices [i], sizeof (G3::VEC3F)))
                throw G3::EX_INT ("Failed to deflate vertex no. ", i, FILE_LINE);
        }
        // G3::VEC2F texcoords
        for (i=0; i<(int)texcoords.size (); i++) {
            if (!zStream->Deflate ((byte *) &texcoords [i], sizeof (G3::VEC2F)))
                throw G3::EX_INT ("Failed to deflate texcoord no. ", i, FILE_LINE);
        }
        // G3::VEC3F normals
        for (i=0; i<(int)normals.size (); i++) {
            if (!zStream->Deflate ((byte *) &normals [i], sizeof (G3::VEC3F)))
                throw G3::EX_INT ("Failed to deflate normal no. ", i, FILE_LINE);
        }
        // G3::VEC3F tangents
        for (i=0; i<(int)tangents.size (); i++) {
            if (!zStream->Deflate ((byte *) &tangents [i], sizeof (G3::VEC3F)))
                throw G3::EX_INT ("Failed to deflate tangent no. ", i, FILE_LINE);
        }
        // int indexes
        for (i=0; i<(int)indexes.size (); i++) {
            if (!zStream->Deflate ((byte *) &indexes [i], sizeof (int)))
                throw G3::EX_INT ("Failed to deflate index no. ", i, FILE_LINE);
        }

        writeMaterials ();
        writeNodes ();
        writeLeaves ();
        writeBatches ();

        byte d = 0xFF;

        zStream->Deflate ((byte *)&d, sizeof (byte));

		// Flush
        zStream->Deflate (NULL, 0);
        zStream->Close ();

        unzStream->Seek (0, STREAM::STREAM_BEGINNING);

        // Write the whole stream into the file
        if (!unzStream->ReadToFile (filename))
            throw G3::EX_STR ("Failed to write to file ", filename, FILE_LINE);

    } catch (G3::EX ex) {
        if (log)
            log->Report ("OCT_OBJECT::save: %s..\n", ex.toString ().c_str ());

        zStream->Close ();
        unzStream->Flush ();
        delete zStream;
        delete unzStream;
        zStream = NULL;
        unzStream = NULL;

        return false;
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::save: Failed to save file \"%s\"..\n", filename.c_str ());

        zStream->Close ();
        unzStream->Flush ();
        delete zStream;
        delete unzStream;
        zStream = NULL;
        unzStream = NULL;

        return false;
    }

    unzStream->Flush ();
    delete zStream;
    delete unzStream;
    zStream = NULL;
    unzStream = NULL;

    return true;
}

//==============================================================================
// Checks the OCT signature
//==============================================================================
bool G3::OCT_FILE_HEADER::check_magic () {
    return (magic [0] == 'O' && magic [1] == 'C' && magic [2] == 'T' && magic [3] == '4');
}

//==============================================================================
// Dumps the OCT header
//==============================================================================
void G3::OCT_FILE_HEADER::dump (G3::LOG *log) {
    if (log) {
        log->Report ("Magic: %c%c%c%c\n", magic [0], magic [1], magic [2], magic [3]);

        log->Report ("Vertices: %d\n", num_verts);
        log->Report ("Texels: %d\n", num_texels);
        log->Report ("Normals: %d\n", num_normals);
        log->Report ("Tangents: %d\n", num_tangents);
        log->Report ("Indexes: %d\n", num_indexes);
        log->Report ("Materials: %d\n", num_materials);
        log->Report ("Materialchunk size: %ld\n", materialchunk_size);
        log->Report ("Nodes: %d\n", num_nodes);
        log->Report ("Leaves: %d\n", num_leaves);
        log->Report ("Batches: %d\n", num_batches);
    }
}

//==============================================================================
// Reads the OCT header
//==============================================================================
void G3::OCT_OBJECT::readHeader () {
    Assert (unzStream, "Attempting to read from a non-existant stream..");

    // Read the magic OCT3
    if (!unzStream->Read ((byte *) &fHeader, sizeof (OCT_FILE_HEADER), true))
        throw G3::EX ("Failed to read file header", FILE_LINE);

    fHeader.dump (&G3::MainLog);

    // Check the magic
    if (!fHeader.check_magic ())
        throw G3::EX ("The file doesn't match the magic signature", FILE_LINE);
}

//==============================================================================
// Reads OCT materials
//==============================================================================
void G3::OCT_OBJECT::readMaterials () {
    Assert (unzStream, "Attempting to read from a non-existant stream..");

    std::string mat;
    char c = 0;

    // Read the materials
    for (int i=0; i<(int)fHeader.num_materials; i++) {
        while (true) {
            if (!unzStream->Read ((byte *) &c, 1, true))
                throw G3::EX_INT ("Failed to read material path nr. ", i, FILE_LINE);

            if (c == '\0') {
                material_files.push_back (mat);

                printf ("Mat %d: \"%s\"\n", i, mat.c_str ());
                mat = "";
                break;
            }

            mat += c;

            if (unzStream->EndOfFile ())
                break;
        }
    }
}

//==============================================================================
// Reads OCT nodes
//==============================================================================
void G3::OCT_OBJECT::readNodes () {
    Assert (unzStream, "Attempting to read from a non-existant stream..");

    OCT_FILE_NODE node;
    OCT_NODE onode;
    int i, j;

    for (i=0; i<fHeader.num_nodes; i++) {
        if (!unzStream->Read ((byte *) &node, sizeof (OCT_FILE_NODE), true))
            throw G3::EX_INT ("Failed to read node no. ", i, FILE_LINE);

        onode.BBox = node.BBox;

        for (int j=0; j<8; j++)
            onode.children [j] = node.children [j];

        nodes.push_back (onode);
    }
}

//==============================================================================
// Reads OCT leaves
//==============================================================================
void G3::OCT_OBJECT::readLeaves () {
    Assert (unzStream, "Attempting to read from a non-existant stream..");

    OCT_FILE_LEAF leaf;
    OCT_LEAF oleaf;

    for (int i=0; i<fHeader.num_leaves; i++) {
        if (!unzStream->Read ((byte *) &leaf, sizeof (OCT_FILE_LEAF), true))
            throw G3::EX_INT ("Failed to read leaf no. ", i, FILE_LINE);

        oleaf.BBox = leaf.BBox;
        oleaf.num_batches = leaf.num_batches;
        oleaf.batch_offset = leaf.batch_offset;

        leaves.push_back (oleaf);
    }
}

//==============================================================================
// Reads OCT batches
//==============================================================================
void G3::OCT_OBJECT::readBatches () {
    Assert (unzStream, "Attempting to read from a non-existant stream..");

    OCT_FILE_BATCH fBatch;
    BATCH batch;

    for (int i=0; i<fHeader.num_batches; i++) {
        if (!unzStream->Read ((byte *) &fBatch, sizeof (OCT_FILE_BATCH), true))
            throw G3::EX_INT ("Failed to read batch no. ", i, FILE_LINE);

        batch.BBox = fBatch.BBox;
        batch.matID = fBatch.matID;
        batch.indexStart = fBatch.indexStart;
        batch.numElements = fBatch.numElements;

        G3::MainLog.Report (">> Batch %d NumElements %d\n", i, batch.numElements);

        batches.push_back (batch);
    }
}

//==============================================================================
// Reads the OCT
//==============================================================================
G3::MEDIA_OBJECT::READ_RESULT G3::OCT_OBJECT::read () {
    if (!stream)
        return RR_ERROR;

    // First run ?
    if (!zStream) {
        try {
            // 8 MB of raw unpacked buffer
            unzStream = new G3::CISTREAM (8388608);
        } catch (...) {
            if (log)
                log->Report ("Failed to create a CISTREAM for 8 MB of buffer..\n");
            return RR_ERROR;
        }

        try {
            zStream = new G3::ZIPSTREAM (unzStream);
            if (!zStream->Open (ZSTREAM_INFLATE, 9))
                throw G3::EX ("Failed to open a ZIPSTREAM for level 9 inflation", FILE_LINE);
        } catch (G3::EX ex) {
            if (log)
                log->Report ("OCT_OBJECT::read: %s...\n", ex.toString ().c_str ());
            return RR_ERROR;
        } catch (...) {
            if (log)
                log->Report ("Failed to create a ZIPSTREAM for decompressed streaming..\n");
            return RR_ERROR;
        }
    }

    // Position in the compressed stream
    static long stream_pos = 0;
    // Seek to the position
    stream->Seek (stream_pos, G3::STREAM::STREAM_SET);
    // Get a chunk
    G3::STREAM::CHUNK chunk = stream->GetChunk ();
    // Add its length to the stream position
    stream_pos += chunk.chunkSize;
    // And jump back to the end & wait for some more data
    stream->Seek (0, G3::STREAM::STREAM_END);

    // Was this chunk enough for unpacking some?
    if (!zStream->InflateIn (chunk.chunkPointer, chunk.chunkSize))
        return RR_NEED_MORE;

    // Get the size of uncompressed stream
    long size = unzStream->Tell (G3::STREAM::STREAM_SIZE);
    static long size_left = 0;

    // Data processing stage
    static int stage = 0;

    int i;
    uint id;
    G3::VEC3F v;
    G3::VEC2F uv;

    try {
        // 0 - Header
        size -= sizeof (fHeader);

        if (stage == 0 && size >= 0) {
            printf ("Stage 0: Reading header..\n");
            G3::MainLog.Report ("Stage 0: Reading header..\n");

            // Make sure we're at the beginning of the uncompressed stream
            unzStream->Seek (0, G3::STREAM::STREAM_BEGINNING);
            // Read the header
            readHeader ();
            // Next stage
            stage++;
            // Remember the offset
            size_left += sizeof (fHeader);
        }

        // 1 - Vertices
        size -= fHeader.num_verts * sizeof (G3::VEC3F);

        if (stage == 1 && size >= 0) {
            printf ("Stage 1: Vertices..\n");
            G3::MainLog.Report ("Stage 1: Vertices..\n");

            // Seek to the vertices offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            // Read the vertices
            for (i=0; i<(int)fHeader.num_verts; i++) {
                if (!unzStream->Read ((byte *) &v, sizeof (G3::VEC3F), true))
                    throw G3::EX_INT ("Failed to read vertex nr. ", i, FILE_LINE);

                vertices.push_back (v);
            }
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_verts * sizeof (G3::VEC3F);
        }

        // 2 - TextureCoords
        size -= fHeader.num_texels * sizeof (G3::VEC2F);

        if (stage == 2 && size >= 0) {
            printf ("Stage 2: Texels..\n");
            G3::MainLog.Report ("Stage 2: Texels..\n");

            // Seek to the texels offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            // Read the texcoords
            for (i=0; i<(int)fHeader.num_texels; i++) {
                if (!unzStream->Read ((byte *) &uv, sizeof (G3::VEC2F), true))
                    throw G3::EX_INT ("Failed to read texture coordinate nr. ", i, FILE_LINE);

                texcoords.push_back (uv);
            }
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_texels * sizeof (G3::VEC2F);
        }

        // 3 - Normals
        size -= fHeader.num_normals * sizeof (G3::VEC3F);

        if (stage == 3 && size >= 0) {
            printf ("Stage 3: Normals..\n");
            G3::MainLog.Report ("Stage 3: Normals..\n");

            // Seek to the normals offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            // Read the normals
            for (i=0; i<(int)fHeader.num_normals; i++) {
                if (!unzStream->Read ((byte *) &v, sizeof (G3::VEC3F), true))
                    throw G3::EX_INT ("Failed to read vertex normal nr. ", i, FILE_LINE);

                normals.push_back (v);
            }
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_normals * sizeof (G3::VEC3F);
        }

        // 4 - Tangents
        size -= fHeader.num_tangents * sizeof (G3::VEC3F);

        if (stage == 4 && size >= 0) {
            printf ("Stage 4: Tangents..\n");
            G3::MainLog.Report ("Stage 4: Tangents..\n");

            // Seek to the tangents offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            // Read the tangents
            for (i=0; i<(int)fHeader.num_tangents; i++) {
                if (!unzStream->Read ((byte *) &v, sizeof (G3::VEC3F), true))
                    throw G3::EX_INT ("Failed to read vertex tangent nr.", i, FILE_LINE);

                tangents.push_back (v);
            }
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_tangents * sizeof (G3::VEC2F);
        }

        // 5 - Indexes
        size -= fHeader.num_indexes * sizeof (int);

        if (stage == 5 && size >= 0) {
            printf ("Stage 5: Indexes..\n");
            G3::MainLog.Report ("Stage 5: Indexes..\n");

            // Seek to the indexes offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            // Read the indexes
            for (i=0; i<(int)fHeader.num_indexes; i++) {
                if (!unzStream->Read ((byte *) &id, sizeof (int), true))
                    throw G3::EX_INT ("Failed to read vertex index nr.", i, FILE_LINE);

                indexes.push_back (id);
            }
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_indexes * sizeof (int);
        }

        // 6 - Materials
        size -= fHeader.materialchunk_size;

        if (stage == 6 && size >= 0) {
            printf ("Stage 6: Materials..\n");
            G3::MainLog.Report ("Stage 6: Materials..\n");

            // Seek to the materials offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);
            // Read the materials
            readMaterials ();
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.materialchunk_size;
        }

        // 7 - Nodes
        size -= fHeader.num_nodes * sizeof (OCT_FILE_NODE);

        if (stage == 7 && size >= 0) {
            printf ("Stage 7: Nodes..\n");
            G3::MainLog.Report ("Stage 7: Nodes..\n");

            // Seek to the nodes offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);
            // Read the nodes
            readNodes ();
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_nodes * sizeof (OCT_FILE_NODE);
        }

        // 8 - Leaves
        size -= fHeader.num_leaves * sizeof (OCT_FILE_LEAF);

        if (stage == 8 && size >= 0) {
            printf ("Stage 8: Leaves..\n");
            G3::MainLog.Report ("Stage 8: Leaves..\n");

            // Seek to the leaves offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);
            // Read leaves
            readLeaves ();
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_leaves * sizeof (OCT_FILE_LEAF);
        }

        // 9 - Batches
        size -= fHeader.num_batches * sizeof (OCT_FILE_BATCH);

        if (stage == 9 && size >= 0) {
            printf ("Stage 9: Batches..\n");
            G3::MainLog.Report ("Stage 9: Batches..\n");

            // Seek to the batches offset in the uncompressed stream before reading
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);
            // Read the batches
            readBatches ();
            // Next stage
            stage++;
            // Remember the offset
            size_left += fHeader.num_batches * sizeof (OCT_FILE_BATCH);
        }

        // Close the ZipStream
        if (stage == 10) {
            unzStream->Seek (size_left, G3::STREAM::STREAM_SET);

            printf ("Stage 10: Closing..\n");
            G3::MainLog.Report ("Stage 10: Closing..\n");

            byte c = 0;

            unzStream->Read ((byte *) &c, sizeof (byte), false);

            printf ("<<<Byte 0x%X>>>\n", c);

            fHeader.dump (&G3::MainLog);

            stage = 0;
            size_left = 0;
//            stream->Flush ();
            zStream->Close ();
            unzStream->Flush ();
//            delete stream;
            delete zStream;
            delete unzStream;
            stream = NULL;
            zStream = NULL;
            unzStream = NULL;

            return RR_DONE;
        }

    } catch (G3::EX ex) {
        if (log)
            log->Report ("OCT_OBJECT::read: %s..\n", ex.toString ().c_str ());

		stage = 0;
//        stream->Flush ();
        zStream->Close ();
        unzStream->Flush ();
//        delete stream;
        delete zStream;
        delete unzStream;
        stream = NULL;
        zStream = NULL;
        unzStream = NULL;

        return RR_ERROR;
    }

    // Go back to the end & wait for new data
    unzStream->Seek (0, G3::STREAM::STREAM_END);

    return RR_NEED_MORE;
}

//! \todo Streamed reading
bool G3::OCT_OBJECT::load (const std::string &filename) {
    if (!G3::FileExists (filename)) {
        if (log)
            log->Report ("OCT_OBJECT::load: The file \"%s\" doesn't exist..\n", filename.c_str ());
        return false;
    }

    filepath = filename;

    try {
        if (!stream)
            stream = new G3::CISTREAM (8388608);
        else {
            stream->Flush ();
            delete stream;
            stream = NULL;
        }
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::load: Failed to create a new CISTREAM with 8 MB of buffer..\n");
        return false;
    }

    return true;
}

void G3::OCT_OBJECT::upload_node (int id, G3::VEC3F pos) {
    std::list<int> nodelist;
    std::list<int>::iterator it;
    int offset = 0, num = 0;
    int i;

	// Push the node
    nodelist.push_back (id);
	// Iterate through
    while (!nodelist.empty ()) {
        it = nodelist.begin ();

        // Leaf ?
        if (*it < 0) {
            if ((int) leaves.size () > -(*it)) {
                offset = leaves [-(*it)].batch_offset;
                num = leaves [-(*it)].num_batches;

                // Generate all batchkeys and send them to the sorter
                if ((int) batches.size () > offset + num) {
                    for (i=offset; i<offset+num; i++) {
                        batches [i].genKey (pos);

                        G3::MainBatchSorter.add (&batches [i]);
                    }
                }
            }
        // Node ?
        } else {
            if (!nodes.empty () && (int) nodes.size () > *it - 1) {
                for (i=0; i<8; i++) {
                    if (nodes [*it - 1].children [i] != 0)
                        nodelist.push_back (nodes [*it - 1].children [i]);
                }
            }
        }

        nodelist.pop_front ();
    }
}

void G3::OCT_OBJECT::upload_remove_all () {
    std::list<int> nodelist;
    std::list<int>::iterator it;
    int offset = 0, num = 0;
    int i;

	// Push the root node
    nodelist.push_back (1);
    while (!nodelist.empty ()) {
        it = nodelist.begin ();

        // Leaf ?
        if (*it < 0) {
            if ((int) leaves.size () > -(*it)) {
                offset = leaves [-(*it)].batch_offset;
                num = leaves [-(*it)].num_batches;

                // Generate all batchkeys and send them to the sorter
                if ((int) batches.size () > offset + num) {
                    for (i=offset; i<offset+num; i++)
                        G3::MainBatchSorter.remove (&batches [i]);
                }
            }
        // Node ?
        } else {
            if ((int) nodes.size () > *it - 1)
            for (i=0; i<8; i++) {
                if (nodes [*it - 1].children [i] != 0)
                    nodelist.push_back (nodes [*it - 1].children [i]);
            }
        }

        nodelist.pop_front ();
    }
}

void G3::OCT_OBJECT::load_materials () {
    G3::MATERIAL mat;

    for (int i=0; i<(int)material_files.size (); i++) {
        mat.Load (material_files [i].c_str ());

        materials.push_back (mat);
    }
}

void G3::OCT_OBJECT::update (const G3::FRUSTUM *frust) {
        std::list<int> nodelist;
        std::list<int>::iterator it;
        FRUSTUM_CLASSIFIC classific;
        int i, invalid = 0, num = 0;

        // No rootnode? Only a leaf?
        if (nodes.empty () && !leaves.empty ())
            nodelist.push_back (-1);
        else if (!nodes.empty () && !leaves.empty ())
            nodelist.push_back (1);
        else
            return;

        // Traverse the tree
        while (!nodelist.empty ()) {
            it = nodelist.begin ();

            if (invalid >= (int) nodelist.size ()) {
                nodelist.clear ();
                G3::MainLog.Report ("OCT_OBJECT::update: Too many invalid nodes\n");
                return;
            }

            // Invalid node / leaf index
            if (*it == 0 || (*it - 1 > (int) nodes.size ()) || (-(*it) - 1 > (int) leaves.size ())) {
                invalid++;
                continue;
            }

            if (frust != NULL) {
                // Bounding box classification
                if (*it > 0) {
                    classific = frust->IsBoxIn (nodes [*it - 1].BBox);
                } else {
                    classific = frust->IsBoxIn (leaves [-(*it) - 1].BBox);
                }

                if (classific == G3::GFC_HALF) {
                    //printf ("Node 0x%X is SPANNING\n", *it);
                    // This a node?
                    if (*it > 0) {
                        // Add them children into the queue - we need to check them further
                        for (i=0; i<8; i++) {
                            if (nodes [*it - 1].children [i] != 0)
                                nodelist.push_back (nodes [*it - 1].children [i]);
                        }

                        // Cache visibility
                        nodes [*it - 1].node_vis_cache = classific;
                    } else {
                        // This is an half-visible leaf ?
                        // Upload it (if it's not uploaded yet)..
                        if (leaves [-(*it) - 1].leaf_vis_cache == G3::GFC_OUT) {
                            // Upload this leaf
                            num = leaves [-(*it) - 1].upload (batches, frust->Origin);
                        }

                        // And cache visibility
                        leaves [-(*it) - 1].leaf_vis_cache = classific;
                    }
                } else if (classific == G3::GFC_IN) {
                    //printf ("Node 0x%X is IN\n", *it);
                    // This a node?
                    if (*it > 0) {
                        if (nodes [*it - 1].node_vis_cache != G3::GFC_IN) {
                            // Upload this node
                            num = nodes [*it - 1].upload (nodes, leaves, batches, frust->Origin);
                        }

                        // Cache visibility
                        nodes [*it - 1].node_vis_cache = classific;
                    } else {
                        // A leaf that just became visible?
                        if (leaves [-(*it) - 1].leaf_vis_cache == G3::GFC_OUT) {
                            // Upload this node
                            num = leaves [-(*it) - 1].upload (batches, frust->Origin);
                        }

                        // Cache visibility
                        leaves [-(*it) - 1].leaf_vis_cache = classific;
                    }
                } else {
                    //printf ("Node 0x%X is OUT\n", *it);
                    // This a node?
                    if (*it > 0) {
                        if (nodes [*it - 1].node_vis_cache != classific) {
                            nodes [*it - 1].node_vis_cache = classific;
                            // Remove this node from the rendering list
                            num = nodes [*it - 1].upload_removal (nodes, leaves, batches);
                        }
                    } else {
                        if (leaves [-(*it) - 1].leaf_vis_cache != classific) {
                            leaves [-(*it) - 1].leaf_vis_cache = classific;
                            // Remove this node from the rendering list
                            num = leaves [-(*it) - 1].upload_removal (batches);
                        }
                    }
                }
            } else {
                //G3::MainLog.Report ("Recursive batch addition..\n");
                // This a node?
                if (*it > 0) {
                    nodes [*it - 1].node_vis_cache = G3::GFC_IN;
                    // Upload this node
                    num = nodes [*it - 1].upload (nodes, leaves, batches, G3::VEC3F (0, 0, 0));
                } else {
                    leaves [-(*it) - 1].leaf_vis_cache = G3::GFC_IN;
                    // Upload this node
                    num = leaves [-(*it) - 1].upload (batches, G3::VEC3F (0, 0, 0));
                }
            }

            nodelist.pop_front ();
        }
}

void G3::OCT_OBJECT::debug_render () {
    for (int i=0; i<(int) leaves.size (); i++) {
        switch (leaves [i].leaf_vis_cache) {
            case G3::GFC_HALF:
                leaves [i].BBox.Render (G3_BLUE_3F);
                break;

            case G3::GFC_IN:
                leaves [i].BBox.Render (G3_GREEN_3F);
                break;

            default:
                leaves [i].BBox.Render (G3_RED_3F);
                break;
        }
    }
}

void G3::OCT_OBJECT::build_common () {
    if (vertices.empty ())
        throw G3::EX ("No vertices", FILE_LINE);

    if (indexes.empty ())
        throw G3::EX ("No indexes", FILE_LINE);

    mat = NULL;

    if (!materials.empty ())
        mat = &materials [0];

    uv = NULL;

    if (!texcoords.empty ())
        uv = &texcoords [0];

    n = NULL;

    if (!normals.empty ())
        n = &normals [0];

    t = NULL;

    if (!tangents.empty ())
        t = &tangents [0];
}

void G3::OCT_OBJECT::build_vbo () {
    try {
        build_common ();
    } catch (G3::EX &ex) {
        if (log)
            log->Report ("%s\n", ex.toString ().c_str ());
        return;
    }

    try {
        vbo = new VBO ();

        vbo->log = log;

        vbo->init (vertices.size (), &vertices [0],
                   indexes.size (), &indexes [0],
                   materials.size (), mat, uv, n, t);
        vbo->build ();
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::build_vbo: Failed to create a new VBO\n");
    }
}

void G3::OCT_OBJECT::destroy_vbo () {
    if (vbo == NULL)
        return;

    try {
        delete vbo;
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::destroy_vbo: Failed to destroy the VBO");
    }

    vbo = NULL;
}

void G3::OCT_OBJECT::build_dlist () {
    try {
        build_common ();
    } catch (G3::EX &ex) {
        if (log)
            log->Report ("%s\n", ex.toString ().c_str ());
        return;
    }

    try {
        dlist = new DISPLAYLIST ();

        dlist->log = log;

        dlist->init (vertices.size (), &vertices [0],
                     indexes.size (), &indexes [0],
                     materials.size (), mat, uv, n, t);
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::build_dlist: Failed to create a new DisplayList");
    }
}

void G3::OCT_OBJECT::destroy_dlist () {
    if (dlist == NULL)
        return;

    try {
        delete dlist;
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::destroy_dlist: Failed to destroy the DisplayList object");
    }

    dlist = NULL;
}

void G3::OCT_OBJECT::build_gptr () {
    try {
        build_common ();
    } catch (G3::EX &ex) {
        if (log)
            log->Report ("%s\n", ex.toString ().c_str ());
        return;
    }

    try {
        gptr = new GEOM_POINTER ();

        gptr->log = log;

        gptr->init (vertices.size (), &vertices [0],
                    indexes.size (), &indexes [0],
                    materials.size (), mat, uv, n, t);
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::build_gptr: Failed to create a new Geometry Pointer");
    }
}

void G3::OCT_OBJECT::destroy_gptr () {
    if (gptr == NULL)
        return;

    try {
        delete gptr;
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::destroy_gptr: Failed to destroy the Geometry Pointer object");
    }

    gptr = NULL;
}

bool G3::OCT_OBJECT::clear () {
    destroy_vbo ();
    destroy_dlist ();
	destroy_gptr ();

    if (!vertices.empty ())
        vertices.clear ();
    if (!texcoords.empty ())
        texcoords.clear ();
    if (!normals.empty ())
        normals.clear ();
    if (!tangents.empty ())
        tangents.clear ();
    if (!indexes.empty ())
        indexes.clear ();
    if (!materials.empty ())
        materials.clear ();
    if (!material_files.empty ())
        material_files.clear ();

    try {
        if (stream) {
            stream->Flush ();
            delete stream;
            stream = NULL;
        }
        if (unzStream) {
            unzStream->Flush ();
            delete unzStream;
            unzStream = NULL;
        }
        if (zStream) {
            zStream->Close ();
            delete zStream;
            zStream = NULL;
        }
    } catch (...) {
        if (log)
            log->Report ("OCT_OBJECT::clear: Failed to delete the streams..\n");
    }

    return true;
}

void G3::OCT_OBJECT::dump () {
    int i;

    if (log) {
        log->Report ("Number of nodes: %d\n", nodes.size ());

        for (i=0; i<(int) nodes.size (); i++) {
            log->Report ("\nNode %d:\n", i);

            nodes [i].dump ();
        }

        log->Report ("\nNumber of leaves: %d\n", leaves.size ());

        for (i=0; i<(int) leaves.size (); i++) {
            log->Report ("\nLeaf %d:\n", i);

            leaves [i].dump ();
        }

        log->Report ("\nNumber of batches: %d\n", batches.size ());

        for (i=0; i<(int) batches.size (); i++) {
            log->Report ("\nBatch %d: %s\n", i, batches [i].keyToString ().c_str ());

            log->Report ("BBox: (%f, %f, %f) (%f, %f, %f)\n",
                batches [i].BBox.Minimum.x, batches [i].BBox.Minimum.y, batches [i].BBox.Minimum.z,
                batches [i].BBox.Maximum.x, batches [i].BBox.Maximum.y, batches [i].BBox.Maximum.z);

            log->Report ("Material ID: %d\n", batches [i].matID);
            log->Report ("Start index: %d\n", batches [i].indexStart);
            log->Report ("Number of elements: %d\n", batches [i].numElements);
            log->Report ("Display List ID: %d\n", batches [i].display_list_id);
        }

        log->Report ("\nNumber of vertices: %d\n", vertices.size ());

        for (i=0; i<(int) vertices.size (); i++) {
            log->Report ("\nVertex %d: (%f, %f, %f)\n", i, vertices [i].x, vertices [i].y, vertices [i].z);
        }

        log->Report ("\nNumber of texture coordinates: %d\n", texcoords.size ());

        for (i=0; i<(int) texcoords.size (); i++) {
            log->Report ("\nTexCoord %d: (%f, %f)\n", i, texcoords [i].x, texcoords [i].y);
        }

        log->Report ("\nNumber of normals: %d\n", normals.size ());

        for (i=0; i<(int) normals.size (); i++) {
            log->Report ("\nNormal %d: (%f, %f, %f)\n", i, normals [i].x, normals [i].y, normals [i].z);
        }

        log->Report ("\nNumber of tangents: %d\n", tangents.size ());

        for (i=0; i<(int) tangents.size (); i++) {
            log->Report ("\nTangent %d: (%f, %f, %f)\n", i, tangents [i].x, tangents [i].y, tangents [i].z);
        }

        log->Report ("\nNumber of indexes: %d\n", indexes.size ());

        for (i=0; i<(int) indexes.size (); i++) {
            log->Report ("\nIndex %d: %d\n", i, indexes [i]);
        }

        log->Report ("\nNumber of materials: %d\n", materials.size ());

        for (i=0; i<(int) materials.size (); i++) {
            log->Report ("\nMaterial %d:\n", i);

            materials [i].dump ();
        }

        log->Report ("\nNumber of material files: %d\n", material_files.size ());

        for (i=0; i<(int) material_files.size (); i++) {
            log->Report ("\nMaterial File %d: \"%s\"", i, material_files [i].c_str ());
        }

        log->Report ("\n\nVBO pointer: 0x%X\n", vbo);
        log->Report ("\nDisplay List pointer: 0x%X\n", dlist);
        log->Report ("\nGeometry pointer: 0x%X\n", gptr);
        log->Report ("\nMaterial pointer: 0x%X\n", mat);
        log->Report ("\nTexture coordinates pointer: 0x%X\n", uv);
        log->Report ("\nNormal pointer: 0x%X\n", n);
        log->Report ("\nTangent pointer: 0x%X\n", t);
        log->Report ("\nDecompressive stream pointer: 0x%X\n", zStream);
        log->Report ("\nDecompressed stream pointer: 0x%X\n", unzStream);
    }
}
